package com.dc.toolkit.guava.collect;

import com.google.common.base.Function;
import com.google.common.collect.*;
import com.google.common.primitives.Ints;
import org.junit.Test;

import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentMap;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;

/**
 * Description: Guava_Maps 测试类
 * @author: gdc
 * @date: 2021/5/14
 * @version 1.0
 */
public class MapsTest {

    /**
     * 测试       创建 HashMap
     */
    @Test
    public void testNewHashMap(){
        Map<Object, Object> map1 = Maps.newHashMap();
        Map<Object, Object> map2 = Maps.newHashMap(map1);

        // 使用条件：你确定你的容器会装多少个，不确定就用一般形式的
        // 这个容器超过3个还是会自动扩容的。不用担心容量不够用。默认是分配一个容量为16的数组，不够将扩容
        HashMap<Object, Object> map3 = Maps.newHashMapWithExpectedSize(3);

        LinkedHashMap<Object, Object> linkedHashMap1 = Maps.newLinkedHashMap();
        LinkedHashMap<Object, Object> linkedHashMap2 = Maps.newLinkedHashMap(linkedHashMap1);
        LinkedHashMap<Object, Object> linkedHashMap3 = Maps.newLinkedHashMapWithExpectedSize(3);

        ConcurrentMap<Object, Object> concurrentMap = Maps.newConcurrentMap();
    }

    /**
     * 测试       不可变 Map
     */
    @Test
    public void testImmutableMap() {
        Map<String, Integer> salary = ImmutableMap.<String, Integer> builder()
                .put("John", 1000)
                .put("Jane", 1500)
                .put("Adam", 2000)
                .put("Tom", 2000)
                .build();
        System.out.println(salary);

        ImmutableMap<String, String> immutableMap = ImmutableMap.of("name", "张三");
        System.out.println(immutableMap);
    }
    
    /**
     * 测试       不可变 有序 Map
     */
    @Test
    public void testImmutableStoredMap(){
        ImmutableSortedMap<String, Integer> sortedMap1 = new ImmutableSortedMap
                .Builder<String, Integer>(Ordering.natural())
                .put("John", 1000)
                .put("Jane", 1500)
                .put("Adam", 2000)
                .put("Tom", 2000)
                .build();
        System.out.println(sortedMap1);
        assertThat(sortedMap1.firstKey(), equalTo("Adam"));
        assertThat(sortedMap1.lastKey(), equalTo("Tom"));

        // 创建一个不可变的空 Map
        ImmutableSortedMap<Object, Object> immutableSortedMap1 = ImmutableSortedMap.of();
        System.out.println(immutableSortedMap1);

        // 创建一个不可变的 Map, 并直接赋值
        ImmutableSortedMap<String, Object> immutableSortedMap2 = ImmutableSortedMap.of("name", "张三", "age", 18);
        System.out.println(immutableSortedMap2);
    }

    /**
     * 测试       BiMap, BigMap是双向映射，需要确保 键、值 唯一
     */
    @Test
    public void testBiMap(){
        // HashBiMap.create() 创建 BiMap
        BiMap<String, Integer> words = HashBiMap.create();
        words.put("a", 1);
        words.put("b", 2);
        words.put("c", 3);

        // 通过 key 获取 value
        assertThat(words.get("a"), equalTo(1));
        // inverse().get()  逆向，通过 value 获取 key
        assertThat(words.inverse().get(3), equalTo("c"));

        Map<String, Integer> addMap = Maps.newHashMap();
        addMap.put("d", 4);
        addMap.put("e", 5);
        //addMap.put("f", 5);       // putAll() 时，添加的Map+BiMap,不能存在重复的key和value,否则会报错

        // 向 BiMap 中，添加 Map数据
        words.putAll(addMap);
        System.out.println("biMap=" + words);
        System.out.println("key-value反转集合：" + words.inverse());
        System.out.println("key集合：" + words.keySet());
        System.out.println("value集合：" + words.values());
    }

    /**
     * 测试       MultiMap, 每个键关联多个值
     */
    @Test
    public void testMultiMap(){
        Multimap<String, String> multimap = ArrayListMultimap.create();
        // 添加多个水果
        multimap.put("fruit", "apple");
        multimap.put("fruit", "banana");

        // 添加多个动物
        multimap.put("pet", "cat");
        multimap.put("pet", "dog");

        multimap.put("province", "beijing");
        System.out.println(multimap);

        Collection<String> fruit = multimap.get("fruit");
        System.out.println(fruit);

        Collection<String> province = multimap.get("province");
        System.out.println(province);

        // Multimap 转 Map
        Map<String, Collection<String>> collectionMap = multimap.asMap();
        System.out.println("转换为Map>>" + collectionMap);

        // 判断是否包含key
        boolean containsKey = multimap.containsKey("pet");
        System.out.println("判断是否包含key:" + containsKey);

        // 判断是否包含值
        boolean containsValue = multimap.containsValue("dog");
        System.out.println("判断是否包含value:" + containsValue);

        // 获取所有的keySet, [province, fruit, pet]
        Set<String> keySet = multimap.keySet();
        System.out.println("multimap.keySet()>>" + keySet);

        // 获取所有的 key, [province, fruit x 2, pet x 2]
        Multiset<String> keys = multimap.keys();
        System.out.println("multimap.keys()>>" + keys);
        keys.forEach(System.out::println);

        // 获取所有的 value 集合， [beijing, apple, banana, cat, dog]
        Collection<String> values = multimap.values();
        System.out.println("multimap.values()>>" + values);

        // 获取所有的实体类集合， [province=beijing, fruit=apple, fruit=banana, pet=cat, pet=dog]
        Collection<Map.Entry<String, String>> entries = multimap.entries();
        System.out.println("multimap.entries()>>" + entries);
        entries.forEach(System.out::println);

        // 替换key下的所有数据
        multimap.replaceValues("pet", Lists.newArrayList("小猫", "小狗", "dog"));
        System.out.println("通过key替换所有的value数据>>>" + multimap);

        // 通过key,value 删除单个数据
        multimap.remove("pet", "dog");
        System.out.println("通过key,value 删除单个数据>>>" + multimap);

        // 通过key删除key下的所有数据
        multimap.removeAll("fruit");
        System.out.println("通过key 删除key对应的所有数据>>>" + multimap);

        // 清除所有数据
        multimap.clear();
        System.out.println("清除所有的数据>>" + multimap);
    }

    /**
     * 测试       Multimaps.index()
     *  针对的场景是：有一组对象，它们有共同的特定属性，我们希望按照这个属性的值查询对象，但属性值不一定是独一无二的。
     */
    @Test
    public void testMultimaps_index(){
        List<String> names = Lists.newArrayList("John", "Adam", "Tom");
        Function<String, Integer> function = new Function<String, Integer>() {
            @Nullable
            @Override
            public Integer apply(@Nullable String input) {
                return input.length();
            }
        };

        // 将 字符串,通过长度进行分组
        Multimap<Integer, String> groups = Multimaps.index(names, function);
        System.out.println(groups);
    }

    /**
     * 测试           Multimaps.invertFrom()
     *  鉴于Multimap可以把多个键映射到同一个值，也可以把一个键映射到多个值，反转Multimap也会很有用。
     *  Guava 提供了invertFrom(Multimap toInvert, Multimap dest)做这个操作，并且你可以自由选择反转后的Multimap实现。
     */
    @Test
    public void testMultimaps_invertFrom(){
        Multimap<String, Integer> multimap = ArrayListMultimap.create();
        multimap.putAll("a", Ints.asList(1, 2, 3, 4));
        multimap.putAll("b", Ints.asList(3, 4, 5, 6));
        multimap.putAll("c", Ints.asList(6, 7, 8));
        System.out.println(multimap);

        // 反转结果
        TreeMultimap<Integer, String> inverse = Multimaps.invertFrom(multimap, TreeMultimap.create());
        System.out.println(inverse);
    }
    
    /**
     * 测试           Multimaps.forMap()
     * 与Multimaps.invertFrom结合使用，可以把多对一的Map反转为一对多的Multimap。
     */
    @Test
    public void testMultimaps_ForMap(){
        ImmutableMap<String, Integer> map = ImmutableMap.of("a", 1, "b", 1, "c", 2);
        System.out.println(map);
        SetMultimap<String, Integer> setMultimap = Multimaps.forMap(map);
        HashMultimap<Integer, String> hashMultimap = Multimaps.invertFrom(setMultimap, HashMultimap.create());
        System.out.println(hashMultimap);
    }

    /**
     * 测试       Table,格式：行键_列键_值
     */
    @Test
    public void testTable(){
        // 创建一个空Table
        Table<String, String, Integer> distance = HashBasedTable.create();
        distance.put("London", "Paris", 340);
        distance.put("New York", "Los Angeles", 3940);
        distance.put("London", "New York", 5576);
        System.out.println("返回的所有数据为：" + distance);

        // 获取行信息
        Map<String, Integer> row = distance.row("London");
        System.out.println("获取行信息：" + row);

        // 获取列信息
        Map<String, Integer> column = distance.column("New York");
        System.out.println("获取行信息：" + column);

        // 获取行键集合
        Set<String> rowKeySet = distance.rowKeySet();
        System.out.println("行键集合：" + rowKeySet);

        // 获取列键集合
        Set<String> columnKeySet = distance.columnKeySet();
        System.out.println("列键集合：" + columnKeySet);

        // 获取值集合
        Collection<Integer> values = distance.values();
        System.out.println("值集合：" + values);

        // 获取（行键，列键）：值 三元组集合
        Set<Table.Cell<String, String, Integer>> cells = distance.cellSet();
        System.out.println("（行键，列键）：值 三元组集合:" + cells);

        // 获取 行Map
        Map<String, Map<String, Integer>> rowMap = distance.rowMap();
        System.out.println("行Map:" + rowMap);

        // 获取 列Map
        Map<String, Map<String, Integer>> columnMap = distance.columnMap();
        System.out.println("列Map:" + columnMap);

        // 判断是否存在 行键、列键、值、（行键、列键）组合
        assertThat(distance.containsRow("London"), equalTo(true));
        assertThat(distance.containsColumn("Paris"), equalTo(true));
        assertThat(distance.containsValue(340), equalTo(true));
        assertThat(distance.contains("London", "Paris"), equalTo(true));

        // 判断数量
        assertThat(distance.size(), equalTo(3));

        // 表信息清除
        distance.clear();
        System.out.println("清空后的数据：" + distance);
        assertThat(distance.isEmpty(), equalTo(true));
    }

    /**
     * 测试       ClassToInstanceMap  类作为键映射至对象
     */
    @Test
    public void testClassToInstanceMap(){
        // 创建 ClassInstance Map
        ClassToInstanceMap<Number> numbers = MutableClassToInstanceMap.create();

        // putInstance()    向指定的Class类型中，添加class对象数据
        numbers.putInstance(Integer.class, 1);
        numbers.putInstance(Double.class, 1.5);

        System.out.println(numbers);

        // getInstance()    获取对应Class类型的值
        assertThat(numbers.getInstance(Integer.class), equalTo(1));
        assertThat(numbers.getInstance(Double.class), equalTo(1.5));
    }

    /**
     * 测试       Map 比较差异
     *      entriesInCommon()返回两个map中key和value都相同的记录.
     *      entriesOnlyOnLeft()返回两个map中left - map独有的记录.
     *      entriesOnlyOnRight()返回两个map中right map 独有的记录.
     *      entriesDiffering()返回两个map中key相同value不同的记录.
     *      areEqual()返回两个map是否完全相同 true orElse false.
     */
    @Test
    public void testDifference(){
        Map<String, String> map1 = Maps.newHashMap();
        map1.put("a", "a1");
        map1.put("b", "b1");
        map1.put("c", "c1");
        Map<String, String> map2 = Maps.newHashMap();
        map2.put("a", "a1");
        map2.put("b", "b2");
        map2.put("d", "d2");
        MapDifference<String, String> mapDifference = Maps.difference(map1, map2);

        System.out.println("==================测试diff = " + mapDifference);
        System.out.println("==================测试diff - entriesInCommon()方法===================" + mapDifference.entriesInCommon());
        System.out.println("==================测试diff - entriesOnlyOnLeft()方法===================" + mapDifference.entriesOnlyOnLeft());
        System.out.println("==================测试diff - entriesOnlyOnRight()方法===================" + mapDifference.entriesOnlyOnRight());
        System.out.println("==================测试diff - entriesDiffering()方法===================" + mapDifference.entriesDiffering());
        System.out.println("==================测试diff - areEqual()方法===================" + mapDifference.areEqual());
    }

    /**
     * 测试 Map 数据遍历
     */
    @Test
    public void testForeach(){
        Map<String, Integer> map2 = ImmutableMap.<String, Integer> builder()
                .put("John", 1000)
                .put("Jane", 1500)
                .put("Adam", 2000)
                .put("Tom", 2000)
                .build();

        // 遍历1
        for (Map.Entry map: map2.entrySet()) {
            System.out.printf("key = %s， value = %s%n", map.getKey(), map.getValue());
        }
        System.out.println("--------------------------------------------------------------------");
        // 遍历2
        Iterator<Map.Entry<String, Integer>> iterator = map2.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> entry = iterator.next();
            System.out.printf("key = %s， value = %s%n", entry.getKey(), entry.getValue());
        }
        System.out.println("--------------------------------------------------------------------");
        // 遍历3
        for (Object o : map2.keySet()) {
            System.out.printf("key = %s， value = %s%n", o, map2.get(o));
        }
        System.out.println("--------------------------------------------------------------------");
        // 遍历4
        for (Object value : map2.values()) {
            System.out.printf("value = %s%n", value);
        }
        System.out.println("--------------------------------------------------------------------");
        map2.forEach((k, v) -> {
            System.out.printf("key = %s， value = %s%n", k, v);
        });
    }
}
